#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Description: 安全生产日报统一解析器，支持不同类型文件的解析: excel -> json
主要功能：
1. 安全系统违章清单解析
2. 安委办检查组检查情况解析
3. 设备维修重点关注解析
4. 生产作业重点关注解析
5. 重大工程风险提示解析
"""
import os
import json
from abc import abstractmethod
from datetime import datetime

from loguru import logger
import pandas as pd


class BaseParser:
    # 检测文件是否为HTML格式的伪装xls文件
    def _is_html_like_xls(self, path: str) -> bool:
        try:
            with open(path, 'rb') as f:
                head = f.read(4096).lower()
            return b'<html' in head or b'<table' in head
        except Exception:
            return False

    # 合并多行表头：第1行和第2行组合成完整的列名
    def _merge_multirow_headers(self, title_row, key_row):
        merged_keys = []
        for i, (title_val, key_val) in enumerate(zip(title_row, key_row)):
            # 转为字符串并去空白
            title_str = '' if title_val is None else str(title_val).strip()
            key_str = '' if key_val is None else str(key_val).strip()

            # 生成最终列名
            if title_str == '':
                final_key = key_str if key_str else f'col_{i + 1}'
            elif key_str == '':
                final_key = title_str
            elif title_str == key_str:
                final_key = title_str
            else:
                final_key = f'{title_str}_{key_str}'

            merged_keys.append(final_key)

        return merged_keys

    # 检查是否为空行
    def _is_empty_row(self, values):
        if not values:
            return True
        for v in values:
            if v is None:
                continue
            if isinstance(v, str) and v.strip() == '':
                continue
            return False
        return True

    # 清理表头，移除空值和重复
    def _clean_headers(self, headers):
        cleaned = []
        for i, h in enumerate(headers):
            if h is None or str(h).strip() == '':
                cleaned.append(f'col_{i + 1}')
            else:
                cleaned.append(str(h).strip())
        return cleaned

    # 保存数据到JSON文件的通用方法
    def _save_json_data(self, output_data, output_file_path):
        try:
            # 创建输出目录
            os.makedirs(os.path.dirname(output_file_path), exist_ok=True)

            # 保存到JSON文件
            with open(output_file_path, 'w', encoding='utf-8') as f:
                json.dump(output_data, f, ensure_ascii=False, indent=2)

            logger.info(f"保存JSON文件: {output_file_path}")
            return True
        except Exception as e:
            logger.error(f'保存JSON文件失败: {e}')
            return False

    # 读取Excel文件的通用方法，支持多种格式
    def _read_excel_file(self, input_file, sheet_name=None, prefer_calamine=True):
        try:
            # 1) 某些.xls 实际是 HTML，优先用 read_html 读取
            if self._is_html_like_xls(input_file):
                tables = pd.read_html(input_file, encoding='utf-8', header=None)
                if not tables:
                    raise RuntimeError('未找到表格数据')
                return tables[0].fillna('')

            read_kwargs = {'header': None}
            if sheet_name:
                read_kwargs['sheet_name'] = sheet_name

            # 2) 优先尝试 calamine 引擎，能规避部分 openpyxl 的样式解析报错
            if prefer_calamine:
                try:
                    df = pd.read_excel(input_file, engine='calamine', **read_kwargs)
                    return df.fillna('')
                except Exception as e_cala:
                    logger.debug(f'calamine 读取失败，将尝试 openpyxl: {e_cala}')

            # 3) 回退到 openpyxl
            try:
                engine = 'openpyxl' if input_file.lower().endswith(('.xlsx', '.xlsm', '.xltx', '.xltm')) else None
                df = pd.read_excel(input_file, engine=engine, **read_kwargs)
                return df.fillna('')
            except Exception as e_open:
                logger.debug(f'openpyxl 读取失败，将尝试 xlrd（仅限 .xls）: {e_open}')

            # 4) 最后尝试 xlrd（仅对传统 .xls 有效）
            try:
                df = pd.read_excel(input_file, engine='xlrd', **read_kwargs)
                return df.fillna('')
            except Exception as e_xlrd:
                logger.error(f'xlrd 读取也失败: {e_xlrd}')
                return None

        except Exception as e:
            logger.error(f"读取文件失败：{input_file}")
            logger.error(f'Error: {e}')
            return None

    # 处理多行数据的通用方法
    def _process_multirow_data(self, df, headers, start_row=2):
        records = []
        current_record = None

        for row_idx in range(start_row, df.shape[0]):
            row_values = df.iloc[row_idx].tolist()

            # 如果整行都为空，跳过
            if self._is_empty_row(row_values):
                continue

            # 检查第一列（序号），如果有值说明是新项目的开始
            seq_value = str(row_values[0]).strip() if row_values[0] is not None else ''

            if seq_value and seq_value.isdigit():
                # 保存上一个记录（如果存在）
                if current_record:
                    records.append(current_record)

                # 开始新的记录
                current_record = {}
                for i, header in enumerate(headers):
                    value = row_values[i] if i < len(row_values) else ''
                    current_record[header] = str(value).strip() if value else ''
            else:
                # 续行，合并到当前记录中
                if current_record:
                    for i, header in enumerate(headers):
                        if i < len(row_values) and row_values[i] is not None:
                            value = str(row_values[i]).strip()
                            if value:  # 只有非空值才合并
                                if current_record[header]:
                                    current_record[header] += '\n' + value
                                else:
                                    current_record[header] = value

        # 添加最后一个记录
        if current_record:
            records.append(current_record)

        return records

    @abstractmethod
    def parse(self, input_file_path: str, output_file_path: str, file_type=None) -> bool:
        raise NotImplementedError


# ==================== 解析器1: 安全系统违章清单 ====================
class ViolationFileParser(BaseParser):
    # 解析安全系统违章清单Excel文件
    def parse(self, input_file_path, output_file_path, file_type=None):
        try:
            logger.info(f'正在解析违章清单文件: {input_file_path}')

            # 读取Excel文件
            df = self._read_excel_file(input_file)
            if df is None or df.shape[0] < 2:
                logger.warning(f'  - 警告：文件数据行数不足，跳过处理')
                return False

            # 智能判断表头结构
            title_row = df.iloc[0].tolist()
            non_empty_title = [str(x).strip() for x in title_row if str(x).strip() != '']

            # 根据内容判断表头结构
            if len(set(non_empty_title)) <= 2 and df.shape[0] >= 3:
                # 第1行是表题，第2、3行是表头
                title_text = ' '.join(non_empty_title[:3]) if non_empty_title else ''
                if df.shape[0] >= 4:
                    header_row1 = df.iloc[1].tolist()
                    header_row2 = df.iloc[2].tolist()
                    keys = self._merge_multirow_headers(header_row1, header_row2)
                    data_df = df.iloc[3:].reset_index(drop=True)
                else:
                    keys = df.iloc[1].tolist()
                    data_df = df.iloc[2:].reset_index(drop=True)
            elif df.shape[0] >= 2:
                # 第1、2行都是表头
                key_row = df.iloc[1].tolist()
                keys = self._merge_multirow_headers(title_row, key_row)
                title_text = '安全系统违章清单'
                data_df = df.iloc[2:].reset_index(drop=True)

            # 处理数据行
            records = []
            for _, row in data_df.iterrows():
                values = [row.get(i, '') for i in range(len(keys))]
                if self._is_empty_row(values):
                    continue
                rec = {keys[i]: values[i] if i < len(values) else '' for i in range(len(keys))}
                records.append(rec)

            # 构建输出数据
            output_data = {
                'metadata': {
                    'title': title_text if title_text else '安全系统违章清单',
                    'source_file': os.path.basename(input_file_path),
                    'total_records': len(records),
                    'columns': keys
                },
                'data': records
            }

            # 保存JSON文件
            if self._save_json_data(output_data, output_file_path):
                return True
            return False

        except Exception as e:
            logger.error(f'解析失败: {e}')
            import traceback
            logger.error(f'详细错误: {traceback.format_exc()}')
            return False


# ==================== 解析器2: 安委办检查组检查情况 ====================
class SafetyCheckFileParser(BaseParser):
    def parse(self, input_file_path, output_file_path, file_type):
        try:
            logger.info(f'正在解析文件: {os.path.basename(input_file_path)}')

            # 读取Excel文件
            df = self._read_excel_file(input_file_path)
            if df is None or df.shape[0] < 2:
                logger.warning(f'  - 警告：文件数据行数不足')
                return False

            # 固定结构：第一行是表题，第二行是表头
            headers = self._clean_headers(df.iloc[1].tolist())  # 第二行是表头
            data_df = df.iloc[2:].reset_index(drop=True)  # 从第三行开始是数据

            # 处理数据行
            records = []
            for _, row in data_df.iterrows():
                values = [row.get(i, '') for i in range(len(headers))]
                if self._is_empty_row(values):
                    continue

                record = {}
                for i, header in enumerate(headers):
                    value = values[i] if i < len(values) else ''
                    record[header] = str(value).strip() if value else ''
                records.append(record)

            # 构建输出数据
            output_data = {
                'metadata': {
                    'title': file_type,
                    'source_file': os.path.basename(input_file_path),
                    'total_records': len(records),
                    'columns': headers
                },
                'data': records
            }

            # 保存JSON文件
            if self._save_json_data(output_data, output_file_path):
                return True
            return False

        except Exception as e:
            logger.error(f'  - 解析失败: {e}')
            return False


# ==================== 解析器3: 设备维修重点关注 ====================
class EquipmentMaintenanceFileParser(BaseParser):
    def parse(self, input_file_path, output_file_path, file_type=None):
        try:
            logger.info(f'正在解析文件: {os.path.basename(input_file_path)}')

            # 使用calamine引擎读取Excel文件
            try:
                excel_file = pd.ExcelFile(input_file_path, engine='calamine')
                sheet_names = excel_file.sheet_names
            except Exception as e:
                logger.warning(f'calamine 引擎不可用或读取失败，尝试使用 openpyxl 读取: {e}')
                try:
                    excel_file = pd.ExcelFile(input_file_path, engine='openpyxl')
                    sheet_names = excel_file.sheet_names
                except Exception as e2:
                    logger.error(f'读取Excel文件失败: {e2}')
                    return False

            # 查找8.11相关的sheet
            target_sheet_name = None
            for sheet_name in sheet_names:
                if '8.11' in str(sheet_name) or '8月11' in str(sheet_name):
                    target_sheet_name = sheet_name
                    break

            if not target_sheet_name:
                logger.warning(f' - 未找到8.11相关的sheet，可用的sheet: {sheet_names}')
                return False

            # 读取目标sheet
            df = self._read_excel_file(input_file_path, target_sheet_name)
            if df is None or df.shape[0] < 3:
                logger.warning(f' - 警告：表格行数不足，需要至少3行（两行表头+数据行）')
                return False

            # 合并前两行作为完整的列名
            row1 = df.iloc[0].tolist()  # 第一行：序号、项目、项目名称、作业地点、项目存在的风险等
            row2 = df.iloc[1].tolist()  # 第二行：单位、名称、地点等细分列名
            headers = self._merge_multirow_headers(row1, row2)

            # 从第3行开始处理数据，一个项目可能占多行
            records = self._process_multirow_data(df, headers, start_row=2)

            # 构建输出数据
            output_data = {
                'metadata': {
                    'title': '设备外委施工项目风险管控台账_8月11日',
                    'source_file': os.path.basename(input_file_path),
                    'parse_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                    'total_records': len(records),
                    'columns': headers,
                    'description': '设备外委施工项目风险管控台账8月11日数据'
                },
                'data': records
            }

            # 保存JSON文件
            if self._save_json_data(output_data, output_file_path):
                return True
            return False

        except Exception as e:
            logger.error(f'  - 解析失败: {e}')
            return False


# ==================== 解析器4: 生产作业重点关注 ====================
class NightSafetyCheckFileParser(BaseParser):
    def _create_unit_record(self, row):
        """创建单位记录"""
        return {
            '单位': row[0],
            '泊位': row[1] if pd.notna(row[1]) else '',
            '船舶作业进度': row[2] if pd.notna(row[2]) else '',
            '人员风险': row[3] if pd.notna(row[3]) else '',
            '货物风险': row[4] if pd.notna(row[4]) else '',
            '环境风险': row[5] if pd.notna(row[5]) else '',
            '其他风险': row[6] if pd.notna(row[6]) else '',
            '重点管控措施': row[7] if pd.notna(row[7]) else '',
            '项目检查人': row[8] if pd.notna(row[8]) else '',
            '库区位置': row[9] if pd.notna(row[9]) else '',
            '库场风险': row[10] if pd.notna(row[10]) else '',
            '控制措施': row[11] if pd.notna(row[11]) else '',
            '项目检查人_库场': row[12] if pd.notna(row[12]) else '',
            '前场在岗人数': row[13] if pd.notna(row[13]) else '',
            '后场在岗人数': row[14] if pd.notna(row[14]) else '',
            '总人数': row[15] if pd.notna(row[15]) else '',
            '港机夜间加班项目': row[16] if pd.notna(row[16]) else '',
            '风险': row[17] if pd.notna(row[17]) else '',
            '管控措施': row[18] if pd.notna(row[18]) else '',
            '加班人数': row[19] if pd.notna(row[19]) else '',
            '预计完成时间': row[20] if pd.notna(row[20]) else ''
        }

    def _extract_shared_data(self, unit_record):
        """提取单位的共享数据"""
        return {
            '重点管控措施': unit_record['重点管控措施'],
            '项目检查人': unit_record['项目检查人'],
            '库区位置': unit_record['库区位置'],
            '库场风险': unit_record['库场风险'],
            '控制措施': unit_record['控制措施'],
            '项目检查人_库场': unit_record['项目检查人_库场'],
            '前场在岗人数': unit_record['前场在岗人数'],
            '后场在岗人数': unit_record['后场在岗人数'],
            '总人数': unit_record['总人数'],
            '港机夜间加班项目': unit_record['港机夜间加班项目'],
            '风险': unit_record['风险'],
            '管控措施': unit_record['管控措施'],
            '加班人数': unit_record['加班人数'],
            '预计完成时间': unit_record['预计完成时间']
        }

    def _create_berth_record(self, row, current_unit, current_unit_data):
        """创建泊位记录"""
        return {
            '单位': current_unit,
            '泊位': row[1],
            '船舶作业进度': row[2] if pd.notna(row[2]) else '',
            '人员风险': row[3] if pd.notna(row[3]) else '',
            '货物风险': row[4] if pd.notna(row[4]) else '',
            '环境风险': row[5] if pd.notna(row[5]) else '',
            '其他风险': row[6] if pd.notna(row[6]) else '',
            '重点管控措施': current_unit_data['重点管控措施'],
            '项目检查人': current_unit_data['项目检查人'],
            '库区位置': row[9] if pd.notna(row[9]) else current_unit_data['库区位置'],
            '库场风险': row[10] if pd.notna(row[10]) else current_unit_data['库场风险'],
            '控制措施': row[11] if pd.notna(row[11]) else current_unit_data['控制措施'],
            '项目检查人_库场': row[12] if pd.notna(row[12]) else current_unit_data['项目检查人_库场'],
            '前场在岗人数': current_unit_data['前场在岗人数'],
            '后场在岗人数': current_unit_data['后场在岗人数'],
            '总人数': current_unit_data['总人数'],
            '港机夜间加班项目': current_unit_data['港机夜间加班项目'],
            '风险': current_unit_data['风险'],
            '管控措施': current_unit_data['管控措施'],
            '加班人数': current_unit_data['加班人数'],
            '预计完成时间': current_unit_data['预计完成时间']
        }

    def parse(self, input_file_path, output_file_path, sheet_name='8.11', file_type=None):
        try:
            # 读取Excel文件（优先 calamine，避免 openpyxl 样式 Fill 报错）
            df = self._read_excel_file(input_file_path, sheet_name=sheet_name)

            # 获取列标题
            headers = df.iloc[0].tolist()
            logger.debug(f"表格列标题: {headers}")

            # 数据存储
            parsed_data = []
            current_unit = None
            current_unit_data = {}

            # 遍历数据行（从第2行开始，跳过标题行）
            for i in range(1, len(df)):
                row = df.iloc[i].tolist()

                # 检查是否为新单位行
                if pd.notna(row[0]) and row[0] not in ['单位']:
                    current_unit = row[0]
                    # 创建新的单位数据记录
                    unit_record = self._create_unit_record(row)
                    # 保存当前单位的共享数据
                    current_unit_data = self._extract_shared_data(unit_record)
                    parsed_data.append(unit_record)

                # 检查是否为泊位行（单位列为空，但泊位列有值）
                elif pd.isna(row[0]) and pd.notna(row[1]) and current_unit:
                    # 创建泊位记录，继承当前单位的数据
                    berth_record = self._create_berth_record(row, current_unit, current_unit_data)
                    parsed_data.append(berth_record)

            if not parsed_data:
                logger.error('解析失败')
                return

            output_data = {
                "metadata": {
                    "source_file": "（6）各单位夜间安全检查重点.xlsx",
                    "sheet_name": "8.11",
                    "total_records": len(parsed_data)
                },
                "data": parsed_data
            }

            # 保存JSON文件
            if self._save_json_data(output_data, output_file_path):
                return True
            return False
        except Exception as e:
            logger.error(f"解析Excel文件失败: {e}")
            return None


# ==================== 解析器5: 重大工程风险提示 ====================
class RiskWarningFileParser(BaseParser):
    def parse(self, input_file_path, output_file_path, file_type=None):
        try:
            logger.info(f'正在解析文件: {os.path.basename(input_file_path)}')

            # 使用calamine引擎读取Excel文件
            excel_file = pd.ExcelFile(input_file_path, engine='calamine')
            sheet_names = excel_file.sheet_names

            # 查找8.11相关的sheet
            target_sheet_name = None
            for sheet_name in sheet_names:
                if '8.11' in str(sheet_name) or '8月11' in str(sheet_name):
                    target_sheet_name = sheet_name
                    logger.info(f'找到目标sheet: {sheet_name}')
                    break

            if not target_sheet_name:
                logger.warning(f'未找到8.11相关的sheet，可用的sheet: {sheet_names}')
                return False

            # 读取目标sheet
            df = self._read_excel_file(input_file_path, target_sheet_name)
            if df is None or df.shape[0] < 3:
                logger.warning(f'警告：表格行数不足，需要至少3行（两行表头+数据行）')
                return False

            # 合并前两行作为完整的列名
            row1 = df.iloc[0].tolist()
            row2 = df.iloc[1].tolist()
            headers = self._merge_multirow_headers(row1, row2)

            logger.debug(f'解析到的列名: {headers}')

            # 从第3行开始处理数据，一个项目可能占多行
            records = self._process_multirow_data(df, headers, start_row=2)

            # 构建输出数据
            output_data = {
                'metadata': {
                    'title': '重大工程外委施工项目风险管控台账_8月11日',
                    'source_file': os.path.basename(input_file_path),
                    'parse_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                    'total_records': len(records),
                    'columns': headers,
                    'description': '重大工程外委施工项目风险管控台账8月11日数据'
                },
                'data': records
            }

            # 保存JSON文件
            if self._save_json_data(output_data, output_file_path):
                return True
            return False

        except Exception as e:
            logger.error(f'解析失败: {e}')
            return False


def parse_excel_file_by_type(file_type, input_file_path, output_file_path):
    """
    按类型分发到对应解析器。
    统一对外返回 bool 表示是否解析成功。
    """
    try:
        if file_type == '安全系统违章清单':
            parser = ViolationFileParser()
            return parser.parse(input_file_path, output_file_path)

        if file_type in ('每日安全检查记录', '巡屏小组检查日报'):
            parser = SafetyCheckFileParser()
            return parser.parse(input_file_path, output_file_path, file_type)

        if file_type == '设备外委施工项目风险管控台账':
            parser = EquipmentMaintenanceFileParser()
            return parser.parse(input_file_path, output_file_path)

        if file_type == '各单位夜间安全检查重点':
            parser = NightSafetyCheckFileParser()
            return parser.parse(input_file_path, output_file_path)

        if file_type == '重大工程外委施工项目风险管控台账':
            parser = RiskWarningFileParser()
            return parser.parse(input_file_path, output_file_path)

        logger.error(f'不支持的文件类型: {file_type}')
        logger.error('支持的类型: [安全系统违章清单, 每日安全检查记录, 巡屏小组检查日报, 设备外委施工项目风险管控台账, 各单位夜间安全检查重点, 重大工程外委施工项目风险管控台账]')
        return False

    except Exception as e:
        logger.error(f'分发处理失败: {e}')
        return False


if __name__ == '__main__':
    """
    主函数：统一解析器入口

    功能：
    1. 显示支持的文件类型
    2. 自动处理所有配置的文件
    3. 输出处理结果和统计信息
    """
    files = {
        '安全系统违章清单': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（1）安全系统系统违章清单.xls',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/安全系统违章清单.json'
        },
        '每日安全检查记录': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（2）每日安全检查记录.xlsx',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/每日安全检查记录.json'
        },
        '巡屏小组检查日报': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（3）巡屏小组检查日报.xlsx',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/巡屏小组检查日报.json'
        },
        '设备外委施工项目风险管控台账': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（4）设备外委施工项目风险管控台账.xlsx',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/设备外委施工项目风险管控台账_8月11日.json'
        },
        '各单位夜间安全检查重点': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（6）各单位夜间安全检查重点.xlsx',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/各单位夜间安全检查重点_8月11日.json'
        },
        '重大工程外委施工项目风险管控台账': {
            'input': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/raw_excel/（7）工程外委施工项目风险管控台账.xlsx',
            'output': '/Users/wzq/Desktop/portAI/portai-server/resources/d_reports/excel2json/工程外委施工项目风险管控台账_8月11日.json'
        }
    }

    for file_type, config in files.items():
        input_file = config['input']
        output_file = config['output']
        logger.info(f'正在处理: {input_file}')

        if not os.path.exists(input_file):
            logger.warning(f'输入文件不存在: {input_file}')
            continue

        success = parse_excel_file_by_type(file_type, input_file, output_file)
        if success:
            logger.success(f'✓ 解析成功')
            print()
        else:
            logger.error(f'✗ 解析失败')
            print()

